package cn.ulyer.common.jpa.service;

import cn.hutool.core.lang.Assert;
import cn.ulyer.common.jpa.repository.BaseRepository;
import cn.ulyer.common.model.AbstractBaseModel;
import cn.ulyer.common.utils.PageResult;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;

public abstract class AbstractService<T, ID> implements BaseService<T, ID> {


    @Autowired
    protected BaseRepository<T, ID> baseRepository;

    @Autowired
    protected JPAQueryFactory jpaQueryFactory;

    @PersistenceContext
    protected EntityManager entityManager;


    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public List<T> findAll() {
        return this.baseRepository.findAll();
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public List<T> findAll(Sort sort) {
        return this.baseRepository.findAll(sort);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public List<T> findAllById(Iterable<ID> ids) {
        return this.baseRepository.findAllById(ids);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public <S extends T> List<S> saveAll(Iterable<S> entities) {
        return this.baseRepository.saveAll(entities);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void flush() {
        this.baseRepository.flush();
    }

    @Transactional(rollbackFor = Throwable.class)
    public <S extends T> S saveAndFlush(S entity) {
        return this.baseRepository.saveAndFlush(entity);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void deleteInBatch(Iterable<T> entities) {
        this.baseRepository.deleteInBatch(entities);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void deleteAllInBatch() {
        this.baseRepository.deleteAllInBatch();
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public T getOne(ID id) {
        return this.baseRepository.getOne(id);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> List<S> findAll(Example<S> example) {
        return this.baseRepository.findAll(example);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
        return this.baseRepository.findAll(example, sort);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Page<T> findAll(Pageable pageable) {
        return this.baseRepository.findAll(pageable);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public <S extends T> S save(S entity) {
        return this.baseRepository.save(entity);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Optional<T> findById(ID id) {
        return this.baseRepository.findById(id);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public boolean existsById(ID id) {
        return this.baseRepository.existsById(id);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public long count() {
        return this.baseRepository.count();
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void deleteById(ID id) {
        this.baseRepository.deleteById(id);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void delete(T entity) {
        this.baseRepository.delete(entity);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void deleteAll(Iterable<? extends T> entities) {
        this.baseRepository.deleteAll(entities);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void deleteAll() {
        this.baseRepository.deleteAll();
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> Optional<S> findOne(Example<S> example) {
        return this.baseRepository.findOne(example);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
        return this.baseRepository.findAll(example, pageable);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> long count(Example<S> example) {
        return this.baseRepository.count(example);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <S extends T> boolean exists(Example<S> example) {
        return this.baseRepository.exists(example);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Optional<T> findOne(Specification<T> spec) {
        return this.baseRepository.findOne(spec);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public List<T> findAll(Specification<T> spec) {
        return this.baseRepository.findAll(spec);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Page<T> findAll(Specification<T> spec, Pageable pageable) {
        return this.baseRepository.findAll(spec, pageable);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public List<T> findAll(Specification<T> spec, Sort sort) {
        return this.baseRepository.findAll(spec, sort);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public long count(Specification<T> spec) {
        return this.baseRepository.count(spec);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Optional<T> findOne(Predicate predicate) {
        return this.baseRepository.findOne(predicate);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Iterable<T> findAll(Predicate predicate) {
        return this.baseRepository.findAll(predicate);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Iterable<T> findAll(Predicate predicate, Sort sort) {
        return this.baseRepository.findAll(predicate, sort);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
        return this.baseRepository.findAll(predicate, orders);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Iterable<T> findAll(OrderSpecifier<?>... orders) {
        return this.baseRepository.findAll(orders);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public Page<T> findAll(Predicate predicate, Pageable pageable) {
        return this.baseRepository.findAll(predicate, pageable);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public long count(Predicate predicate) {
        return this.baseRepository.count(predicate);
    }

    @Override
    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public boolean exists(Predicate predicate) {
        return this.baseRepository.exists(predicate);
    }


    // 自定义的2个方法，用于在事务中获取到JPAQueryFactory，执行自定义的查询逻辑

    @Transactional(rollbackFor = Throwable.class)
    public <R> R apply(Function<JPAQueryFactory, R> function) {
        return function.apply(this.jpaQueryFactory);
    }

    @Transactional(readOnly = true, rollbackFor = Throwable.class)
    public <R> R applyReadOnly(Function<JPAQueryFactory, R> function) {
        return function.apply(this.jpaQueryFactory);
    }


    /**
     * use buildQuery search
     * @param pageable
     * @param queryModel
     * @return
     */
    @Override
    public  PageResult<?> page(Pageable pageable,@Nullable Object queryModel) {
        JPAQuery<?> jpaQuery = buildPageQuery(queryModel);
        Assert.notNull(jpaQuery,"when use page should override buildPageQuery");
        QueryResults<?> results = jpaQuery.offset(pageable.getOffset()).limit(pageable.getPageSize()).fetchResults();
        return PageResult.of((int) results.getTotal(),pageable.getPageSize(),results.getResults());
    }

    protected  JPAQuery<?> buildPageQuery(@Nullable Object queryModel){
        return null;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(T model) {
        Assert.notNull(model);
        if(model instanceof AbstractBaseModel){
            AbstractBaseModel baseModel = (AbstractBaseModel) model;
            baseModel.setUpdateTime(new Date());
        }
        Class<T> clz = (Class<T>) model.getClass();
        Field[] fields = clz.getDeclaredFields();
        String tableName = clz.getSimpleName();
        String idName = "id";
        StringBuilder builder = new StringBuilder();
        builder.append("update ").append(tableName).append(" t ").append(" set ");
        boolean update = false;
        Map<String, Object> params = new HashMap<>();
        for (Field field : fields) {
            Id id = field.getAnnotation(Id.class);
            try {
                field.setAccessible(true);
                Object val = field.get(model);
                if (id != null) {
                    idName = field.getName();
                    params.put(field.getName(),val );
                    if (val == null) {
                        return false;
                    }
                } else {
                    update = true;
                    if(val!=null){
                        params.put(field.getName(),val );
                        builder.append(" t.").append(field.getName()).append(" = :").append(field.getName()).append(",");
                    }

                }

            } catch (Exception e) {
                return false;
            }
        }
        builder.replace(builder.lastIndexOf(","),builder.length(),"");
        builder.append(" where  t.").append(idName).append(" = :").append(idName);
        Query query = entityManager.createQuery(builder.toString());
        params.forEach((k, v) -> query.setParameter(k, v));
        return update && query.executeUpdate() > 0;
    }


}
